package nachos.network;

import java.util.*;
import nachos.machine.*;
import nachos.threads.*;
import nachos.userprog.*;
import nachos.vm.*;

/**
 * A <tt>VMProcess</tt> that supports networking syscalls.
 */
public class NetProcess extends UserProcess {
    /**
     * Allocate a new process.
     */
    public NetProcess() {
	super();
	
	// Create a thread to receive data
	//readThread = new ReadThread(pid);
    //Connection.timerThread.addListener( readThread );
	//readThread.fork();

    }

    /**
     * Attempt to initiate a new connection to the specified port on the specified
     * remote host, and return a new file descriptor referring to the connection.
     * connect() does not give up if the remote host does not respond immediately.
     *
     * Returns the new file descriptor, or -1 if an error occurred.
     */
	public int handleConnect(int host, int port)
	{
		// file descriptor to be returned by this syscall
		// TODO: figure out scheme to assign file descriptors
		int fd = assignFileDescriptor();
		
		// Make sure valid fd was assigned
		if (fd == -1)
		{
			return fd;
		}
		
		if ((port >= 0) && (port < MailMessage.portLimit))
		{

	      Lib.debug('n', "Connect got FD #" + fd );
	
	      Connection c = NetKernel.getPostOffice().connect(host, port);
	      if (c.srcPort != -1)
	      {
	       fileDescriptors.put(fd, c);
	
	      //Wait for other side to answer with SYNACK
	      c.connectSemaphore.P();
	      }else
	    	  fd = -1;
	      
		}
		
		return fd;
	}
	
	/**
	 * Attempt to accept a single connection on the specified local port and return
	 * a file descriptor referring to the connection.
	 *
	 * If any connection requests are pending on the port, one request is dequeued
	 * and an acknowledgement is sent to the remote host (so that its connect()
	 * call can return). Since the remote host will never cancel a connection
	 * request, there is no need for accept() to wait for the remote host to
	 * confirm the connection (i.e. a 2-way handshake is sufficient; TCP's 3-way
	 * handshake is unnecessary).
	 *
	 * If no connection requests are pending, returns -1 immediately.
	 *
	 * In either case, accept() returns without waiting for a remote host.
	 *
	 * Returns a new file descriptor referring to the connection, or -1 if an error
	 * occurred.
	 */
	public int handleAccept(int port)
	{
		if ((port >= 0) && (port < MailMessage.portLimit))
		{
	     Connection s = NetKernel.getPostOffice().accept(port);
	
	        if(s != null)
	        {
	            int fd = assignFileDescriptor();
	            fileDescriptors.put(fd, s);
	            return fd;
	        }
		}
        return -1;
	}
	
    @Override
    protected int handleRead(int fd, int vaddr, int count)
    {
        if(fileDescriptors.containsKey(fd))
        {
            Connection c = fileDescriptors.get(fd);
            
            if(!c.transition(Connection.RECV))
                return -1;

            int amount = Math.min(count, c.receiveByteBuffer.size());
            byte[] buffer = new byte[amount];
            int read = 0;
            
            while(read != amount)
            {
                buffer[read] = c.receiveByteBuffer.removeFirst();
                read++;
            }

            int bytesWritten = writeVirtualMemory(vaddr, buffer);
            if (bytesWritten < read)
                return -1;

            return read;
        }

    	return -1;
    }
    
    @Override
    protected int handleWrite(int fd, int vaddr, int count)
    {
    	
        if(fileDescriptors.containsKey(fd))
        {
            Connection c = fileDescriptors.get(fd);

            byte[] data = new byte[count];
            readVirtualMemory(vaddr, data, 0, count);

            return c.write(data, 0, count);
        }
        else
        {
            return -1;
        }
    }
    
    protected int handleClose(int fd)
    {
    	Lib.debug('n', "handleNetClose: " + fd);

        if(fileDescriptors.containsKey(fd))
        {
            fileDescriptors.get(fd).close();
            fileDescriptors.remove(fd);
            return 0;
        }
        else
        {
            return -1;
        }
    }

    /**
     * Handle a syscall exception. Called by <tt>handleException()</tt>. The
     * <i>syscall</i> argument identifies which syscall the user executed:
     *
     * <table>
     * <tr><td>syscall#</td><td>syscall prototype</td></tr>
     * <tr><td>11</td><td><tt>int  connect(int host, int port);</tt></td></tr>
     * <tr><td>12</td><td><tt>int  accept(int port);</tt></td></tr>
     * </table>
     * 
     * @param	syscall	the syscall number.
     * @param	a0	the first syscall argument.
     * @param	a1	the second syscall argument.
     * @param	a2	the third syscall argument.
     * @param	a3	the fourth syscall argument.
     * @return	the value to be returned to the user.
     */
    public int handleSyscall(int syscall, int a0, int a1, int a2, int a3) {
		switch (syscall) {
            case syscallRead:
                if(a0 >= 20)
                {
                    return handleRead(a0, a1, a2);
                }
                else
                {
                    return super.handleRead(a0, a1, a2);
                }
            case syscallWrite:
                if(a0 >= 20)
                {
                    return handleWrite(a0, a1, a2);
                }
                else
                {
                    return super.handleWrite(a0, a1, a2);
                }
            case syscallClose:
                if(a0 >= 20)
                {
                    return handleClose(a0);
                }
                else
                {
                    return super.handleClose(a0);
                }
			case syscallConnect:
		        return handleConnect(a0, a1);
			case syscallAccept:
		        return handleAccept(a0);
			default:
			    return super.handleSyscall(syscall, a0, a1, a2, a3);
		}
    }

    /**
     * @return file descriptor
     */
    private static int assignFileDescriptor() {
       return nextFileDescriptor++;
    }
    
    public Connection[] getConnectionArray(){
    	return connectionArray;
    }
    
    // syscalls
    private static final int
     syscallRead = 6,
	 syscallWrite = 7,
	 syscallClose = 8,
     syscallConnect = 11,
     syscallAccept = 12;
    
    // array of connections for this process
    // TODO: Is there a limit to connections? Is it the same as limit for files (fd's)?
    // If not, maybe make it a linked list of Connections
    public final int MAX_CONNECTIONS = 50;
    protected Connection[] connectionArray = new Connection[MAX_CONNECTIONS];

    private static int nextFileDescriptor = 20;
    private Hashtable<Integer,Connection> fileDescriptors = new Hashtable<Integer,Connection>();
    
    protected ReadThread readThread;
    
    private static final char dbgNet = 's';

}
